package com.lagou.mvc.servlet;

import com.lagou.mvc.annotations.*;
import com.lagou.mvc.pojo.Access;
import com.lagou.mvc.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

public class DispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();
    private List<String> classNames = new ArrayList<>();
    // ioc 容器
    private Map<String, Object> iocMap = new HashMap<String, Object>();
    // handlerMapping
    // url 和 method 映射关系
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) {
        // 1. 加载配置文件 springmvc.properties
        String location = config.getInitParameter("contextConfigLocation");
        doLoadConfig(location);
        // 2. 扫描相关类，扫描注解
        doScan(properties.getProperty("scanPackage"));
        // 3. 初始化Bean对象（基于注解，IOC容器）
        doInstance();
        // 4. 实现依赖注入
        doAutowired();
        // 5. 构造一个handlerMapping处理器映射器，将配置好的url和method建立映射关系
        initHandlerMapping();
        // 6. 构建权限关系
        initSecurity();
        // 等待请求进入，处理请求
    }

    private void addToSet(Set<String> set, String[] value) {
        if (value.length > 0) {
            set.addAll(Arrays.asList(value));
        }
    }

    private void initSecurity() {
        for (Handler handler : handlerMapping) {
            boolean permissionEnable = false;
            Set<String> allowSet = new HashSet<>();
            Object controller = handler.getController();
            Class<?> aClass = controller.getClass();
            if (aClass.isAnnotationPresent(Security.class)) {
                permissionEnable = true;
                addToSet(allowSet, aClass.getAnnotation(Security.class).value());
            }
            Method method = handler.getMethod();
            if (method.isAnnotationPresent(Security.class)) {
                permissionEnable = true;
                addToSet(allowSet, method.getAnnotation(Security.class).value());
            }
            if (permissionEnable) {
                Access access = new Access(allowSet);
                handler.setAccess(access);
            }
        }
    }

    private void initHandlerMapping() {
        if (iocMap.isEmpty()) return;
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (aClass.isAnnotationPresent(Controller.class)) {
                String baseUrl;
                if (aClass.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping requestMapping = aClass.getAnnotation(RequestMapping.class);
                    baseUrl = requestMapping.value();

                    Method[] methods = aClass.getMethods();
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(RequestMapping.class)) {
                            RequestMapping mapping = method.getAnnotation(RequestMapping.class);
                            String url = baseUrl + mapping.value();
                            Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));

                            Parameter[] parameters = method.getParameters();
                            // 计算方法的参数信息
                            for (int i = 0; i < parameters.length; i++) {
                                Parameter parameter = parameters[i];
                                if (parameter.getType() == HttpServletRequest.class ||
                                        parameter.getType() == HttpServletResponse.class) {
                                    handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                                } else {
                                    handler.getParamIndexMapping().put(parameter.getName(), i);
                                }
                            }

                            handlerMapping.add(handler);
                        }
                    }
                }
            }

        }
    }

    private void doAutowired() {
        if (iocMap.isEmpty()) return;
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    String beanName;
                    String value = autowired.value();
                    if ("".equals(value)) {
                        beanName = field.getName();
                    } else {
                        beanName = value;
                    }
                    field.setAccessible(true);
                    try {
                        field.set(entry.getValue(), iocMap.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    private void doInstance() {
        if (classNames.size() == 0) return;
        try {
            for (String className : classNames) {
                Class<?> aClass = Class.forName(className);
                if (aClass.isAnnotationPresent(Controller.class)) {
                    String simpleName = aClass.getSimpleName();
                    String key = lowerFirst(simpleName);
                    Object instance = aClass.newInstance();
                    iocMap.put(key, instance);
                } else if (aClass.isAnnotationPresent(Service.class)) {
                    Service service = aClass.getAnnotation(Service.class);
                    String value = service.value();
                    String beanName;
                    if (!"".equals(value)) {
                        beanName = value;
                    } else {
                        beanName = lowerFirst(aClass.getSimpleName());
                    }
                    Object instance = aClass.newInstance();
                    iocMap.put(beanName, instance);
                    for (Class<?> anInterface : aClass.getInterfaces()) {
                        iocMap.put(anInterface.getName(), instance);
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public String lowerFirst(String s) {
        char[] chars = s.toCharArray();
        if (chars[0] >= 'A' && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                doScan(scanPackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    private void doLoadConfig(String location) {
        InputStream stream = this.getClass().getClassLoader().getResourceAsStream(location);
        try {
            properties.load(stream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理请求
        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }
        // 权限校验
        String username = req.getParameter("username");
        if (handler.getAccess() != null && !handler.getAccess().allow(username)) {
            resp.getWriter().write("403 forbidden");
            return;
        }

        // 参数绑定
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        Object[] args = new Object[parameterTypes.length];
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            String value = StringUtils.join(param.getValue(), ",");
            if (handler.getParamIndexMapping().containsKey(param.getKey())) {
                Integer idx = handler.getParamIndexMapping().get(param.getKey());
                args[idx] = value;
            }
        }
        if (handler.getParamIndexMapping().containsKey(HttpServletRequest.class.getSimpleName())) {
            Integer index = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
            args[index] = req;
        }
        if (handler.getParamIndexMapping().containsKey(HttpServletResponse.class.getSimpleName())) {
            Integer index = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
            args[index] = resp;
        }
        // 目标方法调用
        try {
            Object invoke = handler.getMethod().invoke(handler.getController(), args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) return null;
        String uri = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            if (handler.getPattern().matcher(uri).matches()) {
                return handler;
            }
        }
        return null;
    }
}
